/**
 * BibSonomy-Rest-Common - Common things for the REST-client and server.
 *
 * Copyright (C) 2006 - 2016 Knowledge & Data Engineering Group,
 *                               University of Kassel, Germany
 *                               http://www.kde.cs.uni-kassel.de/
 *                           Data Mining and Information Retrieval Group,
 *                               University of Würzburg, Germany
 *                               http://www.is.informatik.uni-wuerzburg.de/en/dmir/
 *                           L3S Research Center,
 *                               Leibniz University Hannover, Germany
 *                               http://www.l3s.de/
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.bibsonomy.rest.utils;

import static org.bibsonomy.util.ValidationUtils.present;

import java.io.UnsupportedEncodingException;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bibsonomy.util.StringUtils;

/**
 * @author dzo
 * @author rja
 */
public class HeaderUtils {
	private final static Log log = LogFactory.getLog(HeaderUtils.class);
	
	/** the header key for authorization */
	public static final String HEADER_AUTHORIZATION = "Authorization";
	
	/** the header key for user agent */
	public static final String HEADER_USER_AGENT = "User-Agent";
	
	/** the header key for accept */
	public static final String HEADER_ACCEPT = "Accept";
	
	private static final String HEADER_AUTH_BASIC = "Basic ";

	private HeaderUtils() {}

	/**
	 * 
	 * @param acceptHeader 
	 * 			the HTML ACCEPT Header
	 * 			<br/>example: 
	 * 				<code>ACCEPT: text/xml,text/html;q=0.9,text/plain;q=0.8,image/png</code>
	 * 				would be interpreted in the following precedence:
	 *              <ol>
	 * 				<li>text/xml</li>
	 * 				<li>image/png</li>
	 * 				<li>text/html</li>
	 * 				<li>text/plain</li>
	 *              </ol>
	 * 			) 	
	 * @return a sorted map with the precedences
	 */
	public static SortedMap<Double, List<String>> getPreferredTypes(final String acceptHeader) {
		// maps the q-value to output format (reverse order)
		final SortedMap<Double,List<String>> preferredTypes = new TreeMap<Double,List<String>>(new Comparator<Double>() {
			@Override
			public int compare(Double o1, Double o2) {
				if (o1.doubleValue() > o2.doubleValue())
					return -1;
				else if (o1.doubleValue() < o2.doubleValue())
					return 1;
				else
					return o1.hashCode() - o2.hashCode();
			}
		});
		
		if (!present(acceptHeader)) {
			return preferredTypes;
		}
	
		// fill map with q-values and formats
		final Scanner scanner = new Scanner(acceptHeader.toLowerCase());
		scanner.useDelimiter(",");
	
		while(scanner.hasNext()) {
			final String[] types = scanner.next().split(";");
			final String type = types[0];
			double qValue = 1;
	
			if (types.length != 1 && types[1].indexOf('=') > 0) {
				/*
				 * FIXME: we get 
				 *   java.lang.NumberFormatException: For input string: "screen"
				 * in the error log because the format we assume seems to be 
				 * different by some clients. Until we find out, what is really 
				 * wrong (our parsing or the client), we are more careful with
				 * parsing external data.
				 */
				try {
					qValue = Double.parseDouble(types[1].split("=")[1]);
				} catch (final NumberFormatException nfe) {
					qValue = 0;
					log.error("NFCouldn't parse accept header '"+acceptHeader+"'");
				} catch (final ArrayIndexOutOfBoundsException aie) {
					qValue = 0;
					log.error("AICouldn't parse accept header '"+acceptHeader+"'", aie);
				}
			}
			
			
			if (!preferredTypes.containsKey(qValue)) {
				preferredTypes.put(qValue, new LinkedList<String>());
			}
			preferredTypes.get(qValue).add(type);
			
		}
		return preferredTypes;
	}

	/**
	 * Encode the username and password for BASIC authentication
	 * @param username	the username
	 * @param password 	the password
	 * 
	 * @return "Basic " + Base64 encoded(username + ':' + password)
	 */
	public static String encodeForAuthorization(final String username, final String password) {
		try {
			return HEADER_AUTH_BASIC + new String(Base64.encodeBase64((username + ":" + password).getBytes()), StringUtils.CHARSET_UTF_8);
		} catch (final UnsupportedEncodingException e) {
		}
		return HEADER_AUTH_BASIC + new String(Base64.encodeBase64((username + ":" + password).getBytes()));
	}
	
	/**
	 * Check whether a request contains HTTP-Basic credentials by looking at 
	 * the request's authorization string
	 * 
	 * @param authentication a request's authorization header
	 * 
	 * @return true, if the given authorization header is a Http Basic authorization header
	 */
	public static boolean isHttpBasicAuthorization(final String authentication) {
		return (present(authentication) && authentication.startsWith(HEADER_AUTH_BASIC));
	}
}
